Análisis Airbnb NY - 2019



In [6]:
from IPython.display import HTML
In [14]:
HTML('''<script>
code_show=true; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>
El código está oculto, para mostrarlo haz click <a href="javascript:code_toggle()">aquí</a>.''')
HTML('''<script>
code_show=true; 
function code_toggle() {
 if (code_show){
 $('div.input').hide();
 } else {
 $('div.input').show();
 }
 code_show = !code_show
} 
$( document ).ready(code_toggle);
</script>
El código está oculto, para mostrarlo haz click  <a href="javascript:code_toggle()">aquí</a>.''')
Out[14]:
El código está oculto, para mostrarlo haz click aquí.

Importación de librerias

In [2]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import geopandas as gdp
import descartes
import mplleaflet
import folium
from shapely.geometry import Point
%matplotlib inline
sns.set_style("whitegrid")
In [3]:
# Reducción de márgenes
from IPython.core.display import display, HTML
display(HTML("<style>.container{ width:78% }</style>"))

Análisis descriptivo dataset New York Airbnb

Creación del dataframe

In [4]:
df = pd.read_csv("./data/airbnb_NYC_2019.csv", sep=",")
df.head()
Out[4]:
id name host_id neighbourhood_group neighbourhood latitude longitude room_type price minimum_nights number_of_reviews last_review reviews_per_month calculated_host_listings_count availability_365
0 2539 Clean & quiet apt home by the park 2787 Brooklyn Kensington 40.64749 -73.97237 Private room 149 1 9 2018-10-19 0.21 6 365
1 2595 Skylit Midtown Castle 2845 Manhattan Midtown 40.75362 -73.98377 Entire home/apt 225 1 45 2019-05-21 0.38 2 355
2 3647 THE VILLAGE OF HARLEM....NEW YORK ! 4632 Manhattan Harlem 40.80902 -73.94190 Private room 150 3 0 NaN NaN 1 365
3 3831 Cozy Entire Floor of Brownstone 4869 Brooklyn Clinton Hill 40.68514 -73.95976 Entire home/apt 89 1 270 2019-07-05 4.64 1 194
4 5022 Entire Apt: Spacious Studio/Loft by central park 7192 Manhattan East Harlem 40.79851 -73.94399 Entire home/apt 80 10 9 2018-11-19 0.10 1 0

Descripción de columnas:

  • id: listing ID
  • name: name of the listing
  • host_id: host ID
  • host_name: name of the host
  • neighbourhood_group: location
  • neighbourhood: area
  • latitude: latitude coordinates
  • longitude: longitude coordinates
  • room_type: listing space type
  • price: price in dollars
  • minimum_nights: amount of nights minimum
  • number_of_reviews: number of reviews
  • last_review: latest review
  • reviews_per_month: number of reviews per month
  • calculated_host_listings_count: amount of listing per host // NUMERO DE APARTAMENTOS QUE TIENE EL ANFITRION
  • availability_365: number of days when listing is available for booking

Si más adelante quisieramos crear modelos de ML, los dos primeros pasos que se van a llevar a cabo son cruciales ya que las métricas podrían verse muy afectadas en función de la imputación que se decida llevar a cabo.

Análisis missing data

In [5]:
df.isnull().any()
Out[5]:
id                                False
name                               True
host_id                           False
neighbourhood_group               False
neighbourhood                     False
latitude                          False
longitude                         False
room_type                         False
price                             False
minimum_nights                    False
number_of_reviews                 False
last_review                        True
reviews_per_month                  True
calculated_host_listings_count    False
availability_365                  False
dtype: bool
In [6]:
df.isnull().sum()/df.shape[0]
Out[6]:
id                                0.000000
name                              0.000327
host_id                           0.000000
neighbourhood_group               0.000000
neighbourhood                     0.000000
latitude                          0.000000
longitude                         0.000000
room_type                         0.000000
price                             0.000000
minimum_nights                    0.000000
number_of_reviews                 0.000000
last_review                       0.205583
reviews_per_month                 0.205583
calculated_host_listings_count    0.000000
availability_365                  0.000000
dtype: float64
In [7]:
for key in df.columns:
    valores_nulos = df[key].isnull().sum()
    proporcion = (valores_nulos / len(df[key]))*100
    print("La variable llamada " + str(key) + "  tiene  " + str(valores_nulos)  + "  valores nulos, en concreto forman un " + str(proporcion) + "%" )
La variable llamada id  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada name  tiene  16  valores nulos, en concreto forman un 0.03272318232948154%
La variable llamada host_id  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada neighbourhood_group  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada neighbourhood  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada latitude  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada longitude  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada room_type  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada price  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada minimum_nights  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada number_of_reviews  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada last_review  tiene  10052  valores nulos, en concreto forman un 20.55833929849678%
La variable llamada reviews_per_month  tiene  10052  valores nulos, en concreto forman un 20.55833929849678%
La variable llamada calculated_host_listings_count  tiene  0  valores nulos, en concreto forman un 0.0%
La variable llamada availability_365  tiene  0  valores nulos, en concreto forman un 0.0%

También se puede analizar desde el punto de vista, proporción de datos conococidos

In [8]:
prop_notna = df.notna().sum()/len(df)

plt.figure(figsize=(10,6))
bar_width = 0.5
positions = np.arange(len(prop_notna)) 
plt.bar(positions, prop_notna, bar_width, alpha=0.25)
plt.title('Proporción de lecturas registradas.', fontsize=5, fontweight='bold');
plt.xlabel('Estación', fontsize=7, fontweight='bold', labelpad=100); 
xticks = positions    
plt.xticks(xticks, prop_notna.index, rotation = 70, fontsize=15);


plt.tick_params(left=False, labelleft=False)

for bar_id in positions:
    plt.text(bar_id, prop_notna.values[bar_id]+0.025,                 
             '{:.1f}%'.format(prop_notna.values[bar_id]*100),          
             horizontalalignment='center', verticalalignment='center', 
             fontsize=11, color='#004D7F',fontweight='bold');          

Se detecta que existen valores faltantes en las variables name, last_review y reviews_per_month y el porcentaje de los mismos. Se decide imputar los valores faltantes con un 0 y conservar dichas observaciones ya que en principio la información que aportan el resto de variables es relevante para el análisis. La descripción (name) no es especialmente relevante, probablemente no lo rellenarián por ahorro de tiempo y en cuanto a las revisiones, es lógico pensar que si falta ese dato se asume que ha sido 0.

In [9]:
df1 = df.fillna(0)
df1.head()
Out[9]:
id name host_id neighbourhood_group neighbourhood latitude longitude room_type price minimum_nights number_of_reviews last_review reviews_per_month calculated_host_listings_count availability_365
0 2539 Clean & quiet apt home by the park 2787 Brooklyn Kensington 40.64749 -73.97237 Private room 149 1 9 2018-10-19 0.21 6 365
1 2595 Skylit Midtown Castle 2845 Manhattan Midtown 40.75362 -73.98377 Entire home/apt 225 1 45 2019-05-21 0.38 2 355
2 3647 THE VILLAGE OF HARLEM....NEW YORK ! 4632 Manhattan Harlem 40.80902 -73.94190 Private room 150 3 0 0 0.00 1 365
3 3831 Cozy Entire Floor of Brownstone 4869 Brooklyn Clinton Hill 40.68514 -73.95976 Entire home/apt 89 1 270 2019-07-05 4.64 1 194
4 5022 Entire Apt: Spacious Studio/Loft by central park 7192 Manhattan East Harlem 40.79851 -73.94399 Entire home/apt 80 10 9 2018-11-19 0.10 1 0
In [11]:
df1.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 48895 entries, 0 to 48894
Data columns (total 15 columns):
id                                48895 non-null int64
name                              48895 non-null object
host_id                           48895 non-null int64
neighbourhood_group               48895 non-null object
neighbourhood                     48895 non-null object
latitude                          48895 non-null float64
longitude                         48895 non-null float64
room_type                         48895 non-null object
price                             48895 non-null int64
minimum_nights                    48895 non-null int64
number_of_reviews                 48895 non-null int64
last_review                       48895 non-null object
reviews_per_month                 48895 non-null float64
calculated_host_listings_count    48895 non-null int64
availability_365                  48895 non-null int64
dtypes: float64(3), int64(7), object(5)
memory usage: 5.6+ MB

Se observa como el número de columnas total es 15 y el número de observaciones es 48.895. Además, se obsera el tipo de valor asignado a cada columna. Es destacable, que el número de reviews_per_month es float, probablemente es debido a que realizaron una media entre todas las reviews reallizadas en un año, por meses.

Análisis de Outliers

Para analizar las variables numericas se van a utilizar gráficos de caja para intentar detectar outliers de manera visual y aproximada.

In [12]:
numericas = df1[["price", "minimum_nights", "number_of_reviews", "reviews_per_month", "calculated_host_listings_count", "availability_365"]]
numericas.head()
Out[12]:
price minimum_nights number_of_reviews reviews_per_month calculated_host_listings_count availability_365
0 149 1 9 0.21 6 365
1 225 1 45 0.38 2 355
2 150 3 0 0.00 1 365
3 89 1 270 4.64 1 194
4 80 10 9 0.10 1 0
In [13]:
numericas_sin_p = df1[["minimum_nights", "number_of_reviews", "reviews_per_month", "calculated_host_listings_count", "availability_365"]]
In [14]:
with sns.axes_style(style='ticks'):
    g = sns.catplot( data= numericas_sin_p, height=10.5, aspect=1,  kind="box",  palette= "BuPu")
In [15]:
with sns.axes_style(style='ticks'):
    g = sns.catplot(data=df1[["price"]], kind="violin", height=10.5, aspect=1, palette= "BuPu")

Dado que la variable price posee gran dispersión se decide visualizarla de manera aislada, con estos gráficos se observan los outliers pero no es suficiente para tomar una decisión definitiva

In [16]:
df1.describe()
Out[16]:
id host_id latitude longitude price minimum_nights number_of_reviews reviews_per_month calculated_host_listings_count availability_365
count 4.889500e+04 4.889500e+04 48895.000000 48895.000000 48895.000000 48895.000000 48895.000000 48895.000000 48895.000000 48895.000000
mean 1.901714e+07 6.762001e+07 40.728949 -73.952170 152.720687 7.029962 23.274466 1.090910 7.143982 112.781327
std 1.098311e+07 7.861097e+07 0.054530 0.046157 240.154170 20.510550 44.550582 1.597283 32.952519 131.622289
min 2.539000e+03 2.438000e+03 40.499790 -74.244420 0.000000 1.000000 0.000000 0.000000 1.000000 0.000000
25% 9.471945e+06 7.822033e+06 40.690100 -73.983070 69.000000 1.000000 1.000000 0.040000 1.000000 0.000000
50% 1.967728e+07 3.079382e+07 40.723070 -73.955680 106.000000 3.000000 5.000000 0.370000 1.000000 45.000000
75% 2.915218e+07 1.074344e+08 40.763115 -73.936275 175.000000 5.000000 24.000000 1.580000 2.000000 227.000000
max 3.648724e+07 2.743213e+08 40.913060 -73.712990 10000.000000 1250.000000 629.000000 58.500000 327.000000 365.000000

Teniendo en cuenta el significado de cada variable, se observa la media con respecto a los valores máximos y mínimos que toma.

Se observan las siguientes cuestiones relevantes:

  • Se observa que existen observaciones en las que el precio es igual a 0, se eliminan esas observaciones ya que propablemente sea un anuncio falso.
  • La variable mínimo de noches, posee un máximo de 1.250 dado que un año posee 365 días (sin tener en cuenta los años bisiestos) se decide eliminar las observaciones que poseean un valor superior a este, ya que no tiene sentido exigir más de un año como mínimo de noches.
  • A través de calculated_host_listings_count se observa como el usuario que más viviendas posee a su nombre es 327 viviendas, pero esto se aleja mucho de la media, en el boxplot se aprecian 2 outliers muy marcados por ello se decide eliminar apartir de 200 y como es obvio como mínimo se posee 1.
  • No tiene sentido que la disponibilidad sea 0, por ello se eliminan estas observaciones, ya que como mínimo tiene sentido que sea 1 día disponible.
  • A través de la desviación típica se observa como las variables que poseen valores más dispersos son el precio y la disponibilidad.
  • Si se quisiera una mayor fiabilidad de la vivienda se podrían eliminar las observaciones que no tengan como mínimo una review, asegurandose así de que no es un anuncio falso, pero dado que no es e objetivo de este análisis, se descarta esa opción
In [17]:
dataset_filtered = df1.loc[(df1['price'] > 0) &
                           (df['minimum_nights'] <= 365) &
                           (df['calculated_host_listings_count'] <= 200) &
                           (df['availability_365'] >= 1)]
In [18]:
dataset_filtered.describe()
Out[18]:
id host_id latitude longitude price minimum_nights number_of_reviews reviews_per_month calculated_host_listings_count availability_365
count 3.078100e+04 3.078100e+04 30781.000000 30781.000000 30781.000000 30781.000000 30781.000000 30781.000000 30781.000000 30781.000000
mean 2.080210e+07 7.980682e+07 40.728383 -73.947870 160.071960 7.805367 32.397291 1.524383 5.304766 173.859426
std 1.148388e+07 8.631966e+07 0.056986 0.051416 255.982596 18.261225 51.956236 1.788312 15.058940 126.007104
min 2.539000e+03 2.571000e+03 40.499790 -74.244420 10.000000 1.000000 0.000000 0.000000 1.000000 1.000000
25% 1.142206e+07 8.280108e+06 40.687920 -73.982360 70.000000 1.000000 2.000000 0.180000 1.000000 54.000000
50% 2.226722e+07 3.952852e+07 40.723750 -73.953480 110.000000 3.000000 11.000000 0.940000 1.000000 164.000000
75% 3.101962e+07 1.408304e+08 40.763430 -73.929460 180.000000 5.000000 40.000000 2.360000 3.000000 302.000000
max 3.648724e+07 2.743213e+08 40.913060 -73.712990 10000.000000 365.000000 629.000000 58.500000 121.000000 365.000000

Se observa como se han reducido 1700 observaciones aproximadamente, consideradas como outliers

Comprobación del número de apariciones de cada anfitrión

Se calcula el host_id (anfitrion) cuantos id tiene a su nombre, es decir cuantos anuncios de casas tiene publicados y se comprueba si es correcta la variable calculated_host_listings_count

In [19]:
host=df1.groupby("host_id").count().loc[:,["id"]].groupby("host_id").count()
final= pd.merge(df1, host, on='host_id').loc[:,["host_id","id_y","calculated_host_listings_count"]].groupby("host_id").count()
final
Out[19]:
id_y calculated_host_listings_count
host_id
2438 1 1
2571 1 1
2787 6 6
2845 2 2
2868 1 1
... ... ...
274273284 1 1
274298453 1 1
274307600 1 1
274311461 1 1
274321313 1 1

37457 rows × 2 columns

In [20]:
final.loc[final.id_y!=final.calculated_host_listings_count,["host_id","id_y","calculated_host_listtings_count"]]
C:\Users\rocio\Anaconda3\lib\site-packages\pandas\core\indexing.py:1418: FutureWarning: 
Passing list-likes to .loc or [] with any missing label will raise
KeyError in the future, you can use .reindex() as an alternative.

See the documentation here:
https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike
  return self._getitem_tuple(key)
Out[20]:
host_id id_y calculated_host_listtings_count
host_id

Con el código anterior se observa como efectivamente el número de viviendas que posee un anfitrión coincide con la columna calculated_host_listings_count

Relación entre variables

Análisis de correspondencias

In [21]:
matrix = np.triu(dataset_filtered.corr())
sns.heatmap(dataset_filtered.corr(), annot=True, mask=matrix,  linewidths=.2, fmt=".1", cmap="BuPu")
Out[21]:
<matplotlib.axes._subplots.AxesSubplot at 0x1aa7a936160>

A través de este gráfico se puede observar la correlación existente entre variables, las que más correlación poseen entre sí son host_id e id y por otro lado reviews_per_month y number_of_reviews, es lógico que esten muy relacionadas entre sí ya que la información que arrojan es muy similar

Análisis de relacionales multidimensional, distribuciones de variables

In [22]:
sns.set(style="ticks", color_codes=True,  palette= "pastel")
g = sns.pairplot(df1)

A través de este gráfico se pueden observar tanto histogramas como gráficos de dispersión y observar las relaciones existentes entre variables. Por ejemplo se puede observar como la variable latitud posee una distribucción de campana de gauss o que los valores de id son totalmente dispersos

Gráfico de dispersión entre precio y barrio

In [23]:
tips = sns.load_dataset("tips")
ax = sns.scatterplot(x= dataset_filtered['neighbourhood_group'], y=dataset_filtered['price'], data=tips,  palette= "pastel")

A través de este gráfico se puede observar la distribución de precios en función de los barrios, siendo Manhattan el más caro y Bronx el más barato con sus respectivos outliers

Gráfico de dispersión entre precio y número de reviews

In [24]:
tips = sns.load_dataset("tips")
ax = sns.scatterplot(x= dataset_filtered['price'], y=dataset_filtered['number_of_reviews'], data=tips,  palette= "pastel")

Como es lógico las viviendas que poseen más reviews son las mas baratas y por consiguiente son las más visitadas

Precios en función de neighbourhood y neighbourhood_group

Precio medio en función de neighbourhood

Enumeración de todos los neighbourhood existentes en el dataset

In [25]:
dataset_filtered['neighbourhood'].unique()
Out[25]:
array(['Kensington', 'Midtown', 'Harlem', 'Clinton Hill', 'Murray Hill',
       "Hell's Kitchen", 'Chinatown', 'Upper West Side', 'South Slope',
       'Williamsburg', 'Fort Greene', 'Chelsea', 'Crown Heights',
       'East Harlem', 'Park Slope', 'Bedford-Stuyvesant',
       'Windsor Terrace', 'Inwood', 'East Village', 'Greenpoint',
       'Bushwick', 'Flatbush', 'Lower East Side',
       'Prospect-Lefferts Gardens', 'Long Island City', 'Kips Bay',
       'SoHo', 'Upper East Side', 'Prospect Heights',
       'Washington Heights', 'Woodside', 'Brooklyn Heights',
       'Carroll Gardens', 'West Village', 'Gowanus', 'Flatlands',
       'Flushing', 'Boerum Hill', 'Sunnyside', 'DUMBO', 'St. George',
       'Highbridge', 'Ridgewood', 'Morningside Heights', 'Jamaica',
       'Middle Village', 'NoHo', 'Ditmars Steinway', 'Cobble Hill',
       'Flatiron District', 'Roosevelt Island', 'Greenwich Village',
       'East Flatbush', 'Tompkinsville', 'Astoria', 'Clason Point',
       'Eastchester', 'Little Italy', 'Kingsbridge', 'Two Bridges',
       'Queens Village', 'Rockaway Beach', 'Forest Hills', 'Nolita',
       'Woodlawn', 'University Heights', 'Gramercy', 'Allerton',
       'East New York', 'Theater District', 'Concourse Village',
       'Sheepshead Bay', 'Emerson Hill', 'Fort Hamilton', 'Bensonhurst',
       'Tribeca', 'Shore Acres', 'Concourse', 'Elmhurst', 'Sunset Park',
       'Brighton Beach', 'Jackson Heights', 'Cypress Hills', 'Arrochar',
       'Financial District', 'Wakefield', 'Clifton', 'Bay Ridge',
       'Graniteville', 'Spuyten Duyvil', 'Stapleton', 'Ozone Park',
       'Columbia St', 'Briarwood', 'Mott Haven', 'Longwood', 'Canarsie',
       'Civic Center', 'East Elmhurst', 'New Springville',
       'Morris Heights', 'Rego Park', 'Arverne', 'Gravesend',
       'Cambria Heights', 'Tottenville', 'Mariners Harbor', 'Concord',
       'Borough Park', 'Bayside', 'Downtown Brooklyn', 'Port Morris',
       'Fieldston', 'Kew Gardens', 'Midwood', 'College Point',
       'Mount Eden', 'Glendale', 'Red Hook', 'Richmond Hill', 'Bellerose',
       'St. Albans', 'Maspeth', 'Williamsbridge', 'Soundview',
       'Woodhaven', 'Battery Park City', 'Co-op City', 'City Island',
       'North Riverdale', 'Dyker Heights', 'Sea Gate', 'Riverdale',
       'Kew Gardens Hills', 'Bay Terrace', 'Norwood', 'Claremont Village',
       'Whitestone', 'Fordham', 'Bayswater', 'Navy Yard', 'Brownsville',
       'Vinegar Hill', 'Eltingville', 'Fresh Meadows', 'Mount Hope',
       'Lighthouse Hill', 'Springfield Gardens', 'Howard Beach',
       'Belle Harbor', 'Jamaica Estates', 'Van Nest', 'West Brighton',
       'Far Rockaway', 'South Ozone Park', 'Corona', 'Great Kills',
       'Manhattan Beach', 'Castleton Corners', 'Stuyvesant Town',
       'East Morrisania', 'Hunts Point', 'Neponsit', 'Pelham Bay',
       'Randall Manor', 'Throgs Neck', 'West Farms', 'Silver Lake',
       'Laurelton', 'Holliswood', 'Pelham Gardens', 'Parkchester',
       'Rosedale', 'Edgemere', 'New Brighton', 'Baychester', 'Bronxdale',
       'Marble Hill', 'Melrose', 'Bergen Beach', 'Richmondtown',
       'Tremont', 'Howland Hook', 'Schuylerville', 'Coney Island',
       'South Beach', 'Bath Beach', 'Midland Beach', 'Oakwood',
       'Castle Hill', 'Hollis', 'Morris Park', 'Douglaston', 'Huguenot',
       'Edenwald', 'Dongan Hills', 'Grant City', 'Port Richmond',
       'Westerleigh', 'Morrisania', 'Westchester Square', 'Little Neck',
       'Fort Wadsworth', 'Rosebank', 'Unionport', 'Mill Basin', 'Belmont',
       'Arden Heights', 'Olinville', 'Grymes Hill', 'Rossville',
       'Breezy Point', "Prince's Bay", 'Willowbrook', 'New Dorp Beach',
       "Bull's Head", 'Todt Hill', 'Jamaica Hills'], dtype=object)
In [26]:
neigh = pd.DataFrame(dataset_filtered.groupby('neighbourhood')['price'].mean())
neigh.describe()
Out[26]:
price
count 218.000000
mean 137.352860
std 100.781933
min 42.666667
25% 82.767045
50% 101.580000
75% 154.511645
max 800.000000
In [27]:
dataset_filtered.groupby('neighbourhood')['price'].aggregate(['min', max, np.median, np.mean,])
Out[27]:
min max median mean
neighbourhood
Allerton 33 450 75.0 91.702703
Arden Heights 70 83 75.0 76.000000
Arrochar 32 625 65.0 115.000000
Arverne 35 1500 125.0 175.095890
Astoria 25 1800 85.0 110.476277
... ... ... ... ...
Willowbrook 249 249 249.0 249.000000
Windsor Terrace 38 450 125.0 142.397849
Woodhaven 10 250 53.0 66.544304
Woodlawn 29 85 68.0 60.111111
Woodside 30 450 65.0 86.320000

218 rows × 4 columns

Precio medio en función de neighbourhood_group
In [28]:
df1['neighbourhood_group'].unique()
Out[28]:
array(['Brooklyn', 'Manhattan', 'Queens', 'Staten Island', 'Bronx'],
      dtype=object)
In [29]:
dataset_filtered.groupby('neighbourhood_group')['price'].aggregate(['min', max, np.median, np.mean])
Out[29]:
min max median mean
neighbourhood_group
Bronx 10 2500 67 89.105148
Brooklyn 10 8000 99 132.927807
Manhattan 10 10000 150 211.658664
Queens 10 2600 75 99.998836
Staten Island 13 5000 75 114.229607

Se observa como de media el neighbourhood_group mas caro es Manhattan y el más barato Bronx.

Es importante tener en cuenta, que en estos casos es más representativo de la realidad la mediana que la media, ya la media esta está muy influenciada por los valores extremos.

Top 10 anfitriones con más reviews

In [30]:
df3 = pd.DataFrame(dataset_filtered.groupby('host_id')['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False)
df3[:10]
Out[30]:
number_of_reviews
host_id
30283594 121
12243051 96
16098958 92
137358866 92
61391963 86
22541573 85
200380610 65
1475015 52
120762452 50
7503643 50

Top 10 barrios con más reviews

In [31]:
df4 = pd.DataFrame(dataset_filtered.groupby(["neighbourhood_group","neighbourhood"])['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False )
df4[:10]
Out[31]:
number_of_reviews
neighbourhood_group neighbourhood
Brooklyn Bedford-Stuyvesant 2477
Williamsburg 2048
Manhattan Harlem 1732
Brooklyn Bushwick 1446
Manhattan Hell's Kitchen 1421
Upper East Side 1079
Upper West Side 1072
Midtown 1069
East Village 943
Brooklyn Crown Heights 911

Se observa como los barrios representados de manera individual en función del número de reviews que poseen, del top 10, 6 pertenecen al grupo de Manhattan

Top 10 grupos de barrios con más reviews

In [32]:
df5 = pd.DataFrame(dataset_filtered.groupby('neighbourhood_group') ['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False)
df5[:10]
Out[32]:
number_of_reviews
neighbourhood_group
Manhattan 12996
Brooklyn 12245
Queens 4296
Bronx 913
Staten Island 331

Grupos de barrios en función del número de anuncios que poseen

In [33]:
with sns.axes_style('white'):
    g = sns.catplot(x="neighbourhood_group", y="id" , data=dataset_filtered, aspect=1,
                       kind="bar", palette= "BuPu")
    g.set_xticklabels(step=1)
In [34]:
with sns.axes_style('white'):
    g = sns.catplot(x="neighbourhood_group", y="id" ,hue="room_type", data=dataset_filtered, aspect=1,
                       kind="bar", palette= "BuPu")
    g.set_xticklabels(step=1)

Se observa como el grupo de barrios que más viviendas publicadas en airbnb posee es Bronx

Número de noches mínimas en función del tipo de habitación

In [35]:
with sns.axes_style('white'):
    g = sns.catplot(x="room_type", y="minimum_nights" ,hue="neighbourhood_group", data=dataset_filtered, aspect=1,
                       kind="bar", palette= "BuPu")
    g.set_xticklabels(step=1)

Distribución de precios según el grupo de barrios y tipo de habitación

In [36]:
with sns.axes_style('white'):
    g = sns.catplot(x="neighbourhood_group", y="price" ,hue="room_type", data=dataset_filtered, aspect=1,
                       kind="bar", palette= "BuPu")
    g.set_xticklabels(step=1)

Serie temporal de las ultimas revisiones en función de la cantidad de anuncios que tienen en esa determinada fecha

In [37]:
n_rev2 = dataset_filtered['last_review'].value_counts().to_frame().iloc[1:1765].sort_index()
In [38]:
n_rev1 = n_rev2.loc[n_rev2.index >= '2018-06-08']
In [39]:
n_rev1['last_review'].plot(figsize = (10,8))
Out[39]:
<matplotlib.axes._subplots.AxesSubplot at 0x1aa01b77940>
In [2]:
# Ampliación de márgenes
from IPython.core.display import display, HTML
display(HTML("<style>.container{ width:98% }</style>"))

Visualización de la localización de cada vivienda a través de Geopandas

Esta libreria permite visualizar todos los ids recogidos en el dataset, que en concreto son 30.781 observaciones y pintarlas todas sobre el mapa de NY. Sin embargo, como defecto posee que es mucho menos visual que las siguientes

In [23]:
mapa=gdp.read_file("geo_export_45c5eca0-1f56-4ba9-94f2-25f768babb92.shp")
fig,ax=plt.subplots(figsize=(20,20))
mapa.plot(ax=ax,alpha=0.5, edgecolors="gray")
Out[23]:
<matplotlib.axes._subplots.AxesSubplot at 0x2618874b828>
In [24]:
geometry = [Point(xy) for xy in zip(dataset_filtered['longitude'], dataset_filtered['latitude'])]
gdf = gdp.GeoDataFrame(dataset_filtered, crs="crs", geometry=geometry)
gdf.plot(marker='o', color='steelblue', markersize=0.2, figsize=(20,20))
Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x2618ac02208>
In [25]:
fig,ax=plt.subplots(figsize=(20,20))
gdf.plot(ax=ax,marker='.', color='steelblue',markersize=20)
mapa.plot(ax=ax,alpha=0.1, edgecolors="k")
Out[25]:
<matplotlib.axes._subplots.AxesSubplot at 0x2618d2cce80>

Visualización de la localización de cada vivienda a través de Mplleaflet

Esta librería nos permite visualizar de manera mucho más exacta cual es la localización del anuncio de airbnb, el problema que tiene es que apartir de unas 1000 observaciones es incapaz de pintarlas y no permite visualizar todas las viviendas. Para solucionar este inconveniente se podría fraccionar el dataset en grupos de barrios y visualizarlos de manera independiente o cualquier otro tipo de segmentación para poder visualizar todas las que interesen. En este ejemplo se visualizan las 1000 primeras observaciones ya que sus posibillidades son muy reducidas

In [42]:
mapa = dataset_filtered[:1000]
mapa
Out[42]:
id name host_id neighbourhood_group neighbourhood latitude longitude room_type price minimum_nights number_of_reviews last_review reviews_per_month calculated_host_listings_count availability_365
0 2539 Clean & quiet apt home by the park 2787 Brooklyn Kensington 40.64749 -73.97237 Private room 149 1 9 2018-10-19 0.21 6 365
1 2595 Skylit Midtown Castle 2845 Manhattan Midtown 40.75362 -73.98377 Entire home/apt 225 1 45 2019-05-21 0.38 2 355
2 3647 THE VILLAGE OF HARLEM....NEW YORK ! 4632 Manhattan Harlem 40.80902 -73.94190 Private room 150 3 0 0 0.00 1 365
3 3831 Cozy Entire Floor of Brownstone 4869 Brooklyn Clinton Hill 40.68514 -73.95976 Entire home/apt 89 1 270 2019-07-05 4.64 1 194
5 5099 Large Cozy 1 BR Apartment In Midtown East 7322 Manhattan Murray Hill 40.74767 -73.97500 Entire home/apt 200 3 74 2019-06-22 0.59 1 129
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1275 548133 Chic One-Bedroom Apt 2694451 Brooklyn Clinton Hill 40.69073 -73.96762 Entire home/apt 135 3 28 2019-06-01 0.40 1 211
1276 548184 Cozy Room in GREENPOINT Apt. YeY 2694253 Brooklyn Greenpoint 40.72515 -73.95153 Private room 199 1 0 0 0.00 1 365
1279 550653 Sun Fill Room in a Spacious Apt 2706505 Brooklyn Windsor Terrace 40.65990 -73.98279 Private room 65 20 6 2018-09-30 0.32 1 311
1280 550777 Lrg1Bdrm, Terrace w/ Cent.Park View 2707053 Manhattan Upper West Side 40.79640 -73.96750 Entire home/apt 210 3 25 2019-06-10 0.30 1 347
1282 552141 Historic sundrenched apt of the Lower East Side 2712998 Manhattan Lower East Side 40.71788 -73.98975 Entire home/apt 300 1 45 2019-06-08 0.62 1 111

1000 rows × 15 columns

In [43]:
mapa.to_csv(index=False, path_or_buf = 'mapa.csv')
In [44]:
from IPython.display import IFrame   
In [45]:
fig = plt.figure(figsize=(8,8))
plt.scatter(mapa['longitude'], mapa['latitude'], alpha=0.5, s=30, c='mediumpurple')
mplleaflet.display(fig=fig)
C:\Users\rocio\Anaconda3\lib\site-packages\IPython\core\display.py:701: UserWarning: Consider using IPython.display.IFrame instead
  warnings.warn("Consider using IPython.display.IFrame instead")
Out[45]:

Para visualizarlo de manera más exhaustiva a través de una ventana emergente

In [46]:
mplleaflet.show(fig=fig)

Visualización de la localización de cada vivienda a través de Folium

Otra alternativa, mucho más completa, es folium. Esta librería utiliza recursos de otra, denominada leaflet que está escrita en Javascript y es una de las de referencia en visualización de datos geospaciales A continuación se mostrará la ubicación de una vivienda en concreto, utilizando una marca.

In [48]:
m = folium.Map(
    location=[39.3014, -72.7390],
    zoom_start=8,
    tiles='Stamen Terrain'
)

folium.Marker(
    location=[40.71788, -73.98975],
    popup=folium.Popup(max_width=450).add_child(
        folium.Vega(mapa, width=450, height=250))
).add_to(m)


m
Out[48]:

Se represetan los ids en el mapa de NY a través de 3 diferentes librerias ya que cada una aporta funcionalidades diferentes.